<?php
namespace Common\Model;

class PlanModel extends Model {
    protected $tableName = 'public.plan';

    public function getTypes() {
        return C('PLAN_TYPE');
    }

    public function getDevices() {
        return C('PLAN_DEVICES');
    }

    /**
     * buildSearchExpr 
     * 组装查询条件
     * 
     * @param mixed $d  查询数据，一般来源于$_GET或$_POST
     * @param mixed $field  可查询的字段对照表，其结构为array(array('表单项目名称' => '数据库字段名称'))
     * @access public
     * @return void 返回值为数组，参见方法内部注释
     */
    public function buildSearchExpr($d, $field = NULL) {
        $expr = [];
        $dao  = [];
        $sortExpr = $this->pk . ' DESC';

        /**
         * 下标为表单中的项目名称，值为数据库中表的字段名称 
         */
        if(empty($field)) {
            $field = [                
                'uid'       => 'uid',
                'user_name' => 'username',
                'name'      => 'name',
                'id'        => 'id',
                'status'    => 'status',
                'clearing'  => 'clearing',
                'type'      => 'type',
            ];
        }

        foreach($field as $k => $v) {
            $val = isset($d[$k]) ? trim($d[$k]) : '';

            if(strlen($val) < 1) {
                continue;
           }

            switch($k) {
                case 'user_name' :
                    $expr['username'] = array('EXP', "LIKE '%{$val}%'");
                    break;

                case 'name' : 
                    $expr['name'] = array('EXP', "LIKE '%{$val}%'");
                    break;

                case 'type' :
                    if (!in_array($val, array_kesy(C('PLAN_TYPE')))) {
                        continue;
                    }

                    $d[$k]        = strtolower($d[$k]);
                    $expr['type'] = $d[$k];
                    break;

                default:
                    $expr[$v] = $d[$k];
            }

            $dao[$k] = $d[$k];
        }

        if(!empty($d['sort']) && in_array($d['sort'], $this->getDbFields())) {
            $sortdir = empty($d['sortdir'])                     ? 'DESC'   : strtoupper($d['sortdir']);
            $sortdir = in_array($sortdir, array('DESC', 'ASC')) ? $sortdir : 'DESC';

            $sort        = $d['sort'];
            $dao['sort'] = $sort;
            $sortExpr    = "{$sort} {$sortdir}";

            $dao['sortdir'] = $sortdir;
        }

        return [            
            'expr' => $expr,            //ThinkPHP查询条件表达式
            'sort' => $sortExpr,        //排序表达式
            'json' => json_encode($dao) //用于回填至查询表单的数据
        ];
    }

    /**
     * checkNameExists
     * 检查计划名称是否已经存在
     *
     * @param mixed $name   计划名称
     * @param mixed $excludeId    需排除的计划id
     * @return void
     */
    public function checkNameExists($name, $excludeId = null) {
        $id   = empty($excludeId) ? 0 : (int)$excludeId;
        $expr = ['name' => $name];

        if (!empty($id)) {
            $expr['id'] = ['NEQ', $id];
        }

        return $this->field('id')->where($expr)->find();
    }

    public function save2($d) {
        if(empty($d['name'])) {
            throw new \Exception('请输入计划名称');
        }

        $id = empty($d['id']) ? 0 : (int)$d['id'];

        if (empty($id)) {
            if (empty($d['uid'])) {
                throw new \Exception('请指定广告主UID');
            }

            /**
             * 编辑时不需要uid
             */
            if (!D('Ader')->getById($d['uid'])) {
                throw new \Exception('未找到广告主信息');
            }
        }


        $expr = ['name' => $d['name']];

        if ($this->checkNameExists($d['name'], $id)) {
            throw new \Exception('计划名称已经存在，请换用其他名称');
        }

        try {
            $d['opts'] = $this->buildOpts($d);
        } catch(\Exception $e) {
            throw new \Exception($e->getMessage());
        }

        $action = 'save';

        if (empty($id)) {
            $d['create_time'] = date('Y-m-d H:i:s');
            $action = 'add';
        }

        try {
            if (!$tmpId = $this->$action($d)) {
                throw new \Exception('计划信息保存出错');
            }

            if($action == 'add') {
                $d['id']  = $tmpId;
            }
        } catch (\Exception $e) {
            throw new \Exception($e->getMessage());
        }

        try {
            cacheApi('plan', [
                'a'  => ($action == 'add' ? 'add' : 'modify'),
                'id' => $d['id'],
            ]);
        } catch(\Exception $e) {
            throw new \Exception($e->getMessage());
        }

        return true;
    }

    /**
     * del
     * 目前只能删除单条数据
     *
     * @param mixed $id
     * @return void
     */
    public function del($id) {
        $id = (int)$id;

        if (!$record = $this->where(['id' => $id])->find()) {
            return true;
        }

        if ($record['status'] == '1') {
            throw new \Exception('计划投放中不能删除');
        }

        $adModel = D('Ad');

        if ($adModel->where(['plan_id' => $id])->find()) {
            throw new \Exception('请选择计划对应的广告素材，然后再删除此计划');
        }

        $expr = ['id' => $id];

        try {
            $this->where($expr)->delete();
        } catch (\Exception $e) {
            throw new Exception($e->getMessage());
        }

        try {
            cacheApi('plan', [
                'a'  => 'del',
                'id' => $id,
            ]);
        } catch(\Exception $e) {
            throw new \Exception($e->getMessage());
        }

        return true;
    }

    /**
     * getById
     * 返回计划基本信息和各种限制投放信息
     *
     * @param mixed $id
     * @return void
     */
    public function getById($id) {
        $id     = (int)$id;
        $record = [];

        if(!$record = $this->where(['id' => $id])->find()) {
            return false;
        }

        if ($opts = json_decode($record['opts'], true)) {
            $record = array_merge($record, $opts);
        }

        return $record;
    }

    /**
     * _options_filter()中设置的有条件，所以在Model::save()方法中不会再设置以主键为条件
     */
    protected function _before_update(&$data, &$options) {
        $pk = $this->getPk();

        if(empty($options['where']) && !isset($data[$pk])) {
            return false;
        }

        if(empty($options['where'])) {
            $options['where'] = array();
        }

        if(!array_key_exists($pk, $options['where']) && !empty($data[$pk])) {
            $options['where'][$pk] = $data[$pk];
        }

        return true;
    }

    protected function _options_filter(&$options) {
        if(!$loginUser = C('RUN_LOGIN_USER')) {
            exit('NO LOGIN');
        }

        if($loginUser['type'] == 2) {
            $options['where']        = isset($options['where']) ? $options['where'] : array();
            $options['where']['uid'] = $loginUser['id'];

            return true;
        }
    }

    /**
     * play
     * 开启计划
     *
     * 会执行以下操作：
     * 检测计划现有状态，如果已经是投放中，则直接返回
     * 检测广告主余额
     * 检测计划限额
     * 检测计划时间限制
     * 检测计划按天限制
     * 检测计划是否到期
     * 所属广告处于停止状态的广告都改成投放状态
     * 更新计费缓存
     *
     * @param mixed $id
     * @return void
     */
    public function play($id) {
        return $this->changeStatus($id, 1);
    }

    /**
     * stop
     * 停止计划
     *
     * 会执行以下操作：
     * 检测计划状态，如果已经是停止状态，则直接返回
     * 停止计划
     * 停止关联的广告
     * 更新计费缓存
     *
     * @param mixed $id
     * @return void
     */
    public function stop($id) {
        return $this->changeStatus($id, 2);
    }

    /**
     * changeUrl
     * 批量修改网址
     * 此方法是针对“批量修改网址”功能的
     *
     * $d = array(
     * 'plan_id'         => '计划编号',
     * 'change_plan_url' => '是否修改计划编号',
     * 'new_url'         => '新网址',
     * 'ad_id'           => array(),
     * )
     *
     * @param mixed $d
     * @return void
     */
    public function changeUrl($d) {
        $id = empty($d['plan_id']) ? 0 : (int)$d['plan_id'];

        if(empty($id)) {
            throw new Exception('没有广告计划编号，无法进行后续操作');
        }

        $planDeviceModel = D2('PlanDevice');

        if (!$planDeviceRecords = $planDeviceModel->where(array('plan_id' => $id))->select()) {
            throw new Exception('没有找到投放设备数据');
        }

        $urls = array(
            'url_1' => '',
            'url_2' => '',
            'url_3' => '',
            'url_4' => '',
        );
        $changePlanUrlStatus = array(); //是否更改计划对应终端网址状态

        foreach ($planDeviceRecords as $v) {
            $_device     = $v['pd_device'];
            $_key        = 'url_' . $_device;
            $_url        = empty($d[$_key]) ? '' : getFullUrl(trim($d[$_key]));
            $urls[$_key] = empty($_url) ? $v['pd_url'] : $_url;

            $_key = 'change_plan_url_' . $_device;
            $changePlanUrlStatus[$_device] = array(
                'pd_id'  => $v['pd_id'],
                'pd_url' => (empty($d[$_key]) ? '' : $_url),
            );
        }

        $adId = $d['ad_id'];

        if(empty($adId)) {
            throw new Exception('请指定需要修改网址的广告');
        }

        C('C_DB_WRITE', 1);
        $this->startTrans();

        /**
         * 更改计划对应终端网址
         */
        foreach ($changePlanUrlStatus as $k => $v) {
            if (empty($v['pd_url'])) {
                continue;
            }

            if ($planDeviceModel->save($v) === false) {
                throw new Exception("修改计划投放终端({$k})网址出错");
            }
        }

        $mAd  = D2('Ad');
        $expr = array('ad_id' => array('IN', $adId));

        $mAd->where($expr)->save($urls);
        $this->commit();

        cacheApi('plan', $id);

        if(!empty($cacheError)) {
            throw new Exception(implode("\n", $cacheError));
        }

        return true;
    }

    /**
     * move
     * 转移计划至指定广告主
     *
     * @param mixed $ids
     * @param mixed $uid
     * @return void
     */
    public function move($ids, $uid) {
        /**
         * 转移计划步骤：
         * 将指定计划的uid和username换成新广告主的uid和username，生成计划缓存
         * 将计划关联的广告素材对应的uid换成新广告主的uid，生成广告缓存
         */
        if(!$ader = D2('Ader')->getById($uid)) {
            throw new Exception('没有找到广告主信息');
        }

        C('C_DB_WRITE', 1);
        $this->startTrans();
        $mAd            = D('Ad');
        $updatedids = array();  //被更新过的计划编号
        $error          = array();

        foreach($ids as $id) {
            if(!$plan = $this->getByid($id)) {
                continue;
            }

            /**
             * 如果待转移计划与接收计划的广告主$uid相同则不转移
             */
            if($plan['uid'] == $uid) {
                continue;
            }

            $planExpr = array('id' => $id);
            $planDao  = array('uid'    => $ader['uid'], 'username' => $ader['user_name']);

            if(!$this->where($planExpr)->save($planDao)) {
                $this->rollback();

                throw new Exception("#{$id} {$plan['planname']}转移出错");
            }

            $updatedids[] = $id;
            $adExpr           = array('plan_id' => $id);
            $adDao            = array('uid'     => $ader['uid']);

            if(!$mAd->where($adExpr)->save($adDao)) {
                $this->rollback();

                throw new Exception("#{$id} {$plan['planname']}更新其所属广告出错");
            }
        }

        $this->commit();

        if(empty($updatedids)) {
            return true;
        }

        foreach($updatedids as $id) {
            try {
                cacheApi('plan', $id);
            } catch(\Exception $e) {
                if(count($error) < 10) {
                    $error[] = $e->getMessage();
                }
            }
        }

        if(!empty($error)) {
            throw new Exception(implode("\n", $error));
        }

        return true;
    }

    /**
     * buildOpts
     * 组装opts自定义选项字段值
     *
     * 如果$d['id']有值，则找出原记录值，然后将新值合并成原opts字段中
     *
     * @param mixed $d
     * @return void
     */
    public function buildOpts($d) {
        $id   = empty($d['id']) ? 0 : $d['id'];
        $opts = [];

        if ($record = $this->getById($id)) {
            if ($_opts = json_decode($record['opts'], true)) {
                $opts = $_opts;
            }
        }

        $opts = array_merge($opts, $this->buildExpireOpts($d));
        $opts = array_merge($opts, $this->buildBudgetOpts($d));
        $opts = array_merge($opts, $this->buildDeviceOpts($d));
        $opts = array_merge($opts, $this->buildBakUrlsOpts($d));

        return json_encode($opts);
    }

    public function buildTimeLimitOpts($d) {
        // code...
    }

    /**
     * buildExpireOpts
     * 组装过期日期选项
     *
     * @param mixed $d
     * @return void
     */
    public function buildExpireOpts($d) {
        $expire = empty($d['expire']) ? '' : $d['expire'];
        $r      = ['expire' => ''];

        if (!empty($expire) || strtotime($expire))  {
            $r['expire'] = $expire;
        }

        return $r;
    }

    /**
     * buildBudgetOpts
     * 组装限额选项
     *
     * @param mixed $d
     * @return void
     */
    public function buildBudgetOpts($d) {
        $budget = empty($d['budget']) ? '' : (int)$d['budget'];

        if (empty($budget)) {
            $budget = '';
        }

        return [
            'budget' => $budget,
        ];
    }

    public function buildWeekLimitOpts() {
        // code...
    }

    /**
     * buildDeviceOpts
     * 组装投放设备选项
     *
     * @param mixed $d
     * @return void
     */
    public function buildDeviceOpts($d) {
        $devices = $this->getDevices();
        $r       = [];

        /**
         * 广告计划指定的投放设备
         */
        $planDevices = empty($d['devices']) ? [] : $d['devices'];

        if (empty($planDevices)) {
            throw new \Exception('请指定投放设备');
        }

        foreach ($d as $k => $v) {
            if (strpos($k, 'url_') === 0) {
                list($_key, $_device) = explode('_', $k);
            } elseif (strpos($k, 'price_') === 0) {
                list($_key, $_i, $_device) = explode('_', $k);
                $_key = $_key . '_' . $_i;
            } elseif (strpos($k, 'deduct_') === 0) {
                list($_key, $_device) = explode('_', $k);
            } else {
                continue;
            }

            /**
             * 如果未选择相应的投放设备，那么此设备对应的配置项目全部忽略
             */
            if (!in_array($_device, $planDevices)) {
                continue;
            }

            $r[$_device][$_key] = $v;
        }

        return ['devices' => $r];
    }

    /**
     * buildBakUrls
     * 组装微信备用网址选项
     *
     * @param mixed $d
     * @return void
     */
    public function buildBakUrlsOpts($d) {
        $url = empty($d['bak_urls']) ? '' : $d['bak_urls'];

        if (!$url) {
            return ['bak_urls' => ''];
        }

        $url  = str_replace("\r", "\n", $url) ;
        $urls = explode("\n", $url);
        $urls = array_filter(array_unique($urls));
        $urls = array_values($urls);

        array_walk($urls, function(&$v) {
            $v = trim($v);

            if (strpos($v, 'http://') !== 0)  {
                if (strpos($v, 'https://') !== 0) {
                    $v = 'http://' . $v;
                }
            }
        });

        return ['bak_urls' => $urls];
    }

    /**
     * parseDeviceOpts
     * 将投放设备选项转换成一维数组，便于表单中使用
     *
     * @param mixed $jsonOpts
     * @return void
     */
    public function parseDeviceOpts($jsonOpts) {
        if (!$opts = json_decode($jsonOpts, true)) {
            return false;
        }

        if (empty($opts['devices'])) {
            return [];
        }

        $r = [];

        foreach ($opts['devices'] as $_device => $_row) {
            foreach ($_row as $k => $v) {
                $_key = $k . '_' . $_device;
                $r[$_key] = $v;
            }
        }

        return $r;
    }

    public function isActive($s) {
        $s = (int)$s;

        return $s == 1 ? true : false;
    }

    public function changeStatus($id, $status) {
        $id     = (int)$id;
        $status = (int)$status;

        if (empty($id)) {
            throw new \Exception('参数不完整');
        }

        if (!$record = $this->where(['id' => $id])->find()) {
            throw new \Exception('没有找到计划数据');
        }

        /**
         * 如果需要变更的状态与表中的记录一致，则不需要执行任何操作
         */
        if ($records['status'] == $status) {
            return true;
        }

        /**
         * 只有开启广告时才检测广告主余额
         */
        if ($status === 1) {
            if ((!$money = D('Ader')->getMoneyById($record['uid'])) || $money < C('ADER_MIN_MONEY')) {
                throw new \Exception('广告主余额不足，无法开启');
            }
        }

        if (!$this->where(['id' => $id])->save(['status' => $status])) {
            return false;
        }

        try {
            cacheApi('plan', [
                'a'  => 'modify',
                'id' => $id,
            ]);
        } catch(\Exception $e) {
            throw new \Exception($e->getMessage());
        }

        return true;
    }

}
